<?php

/**
 * @copyright since 9:07 27/4/2020
 * @author <mc@dancis.info>
 */

namespace app;

class Db extends \mysqli
{
    const DB_HOST = '127.0.0.1'; // Specific for TCP connection rather than socket with `'localhost'` or `null`; depending on driver
    const DB_USER = 'root';
    const DB_USER_PASS = 'root';  // Warning! It just for example. Password should not be marked in the source code but environment variable.
    const DB_NAME = 'dbg_gdnfu';

    const TAB_NAME = 'demo_user';

    public function __construct()
    {
        parent::__construct(
            static::DB_HOST,
            static::DB_USER,
            static::DB_USER_PASS,
            static::DB_NAME
        );
    }

    //region CURD

    public function create()
    {
        $rand = mt_rand();
        list($name, $pass_wd) = [implode('-', ['user', $rand]), password_hash('', PASSWORD_BCRYPT)]; // Hardcoded


        if (!($stmt = $this->prepare('INSERT INTO `' . static::TAB_NAME . '` (`name`, `passwd`) VALUES (?, ?)'))) {
            throw new \Exception('Prepare failed: #' . $this->errno . ' - ' . $this->error);
        }
        if (!$stmt->bind_param('ss', $name, $pass_wd)) {
            throw new \Exception('Bind param failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }

        echo 'Now writing data into database table named ', static::TAB_NAME, PHP_EOL;
        if ($stmt->execute()) {
            return $stmt->affected_rows;
        } else {
            throw new \Exception('Execution failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }
    }

    /**
     * Reset password as empty on the users who's name with specific string.
     * @param string $name
     * @return int
     * @throws \Exception
     */
    public function update($name = 'name')
    {
        // Init. data
        list($name_like, $pass_wd) = ['%' . $name . '%', password_hash('', PASSWORD_BCRYPT)];


        if (!($stmt = $this->prepare('UPDATE `' . static::TAB_NAME . '` SET `passwd` = ? WHERE `name` LIKE ?'))) {
            throw new \Exception('Prepare failed: #' . $this->errno . ' - ' . $this->error);
        }
        if (!$stmt->bind_param('ss', $pass_wd, $name_like)) {
            throw new \Exception('Bind param failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }

        echo 'Now changing password for selected users', PHP_EOL;
        if ($stmt->execute()) {
            return $stmt->affected_rows;
        } else {
            throw new \Exception('Execution failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }
    }

    public function readPassWd($id)
    {
        if (!($stmt = $this->prepare('SELECT `passwd` FROM `' . static::TAB_NAME . '` WHERE `id` = ?'))) {
            throw new \Exception('Prepare failed: #' . $this->errno . ' - ' . $this->error);
        }
        if (!$stmt->bind_param('i', $id)) {
            throw new \Exception('Bind param failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }

        if (!$stmt->execute()) {
            throw new \Exception('Execution failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }

        if (!$stmt->bind_result($pass_wd)) {
            throw new \Exception('Bind result param failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }
        if ($stmt->fetch() === false) {
            throw new \Exception('Fetch result failed: #' . $stmt->errno . ' - ' . $stmt->error);
        } else {
            return $pass_wd;
        }
    }

    public function read($id = null, bool $just_pass_wd = false)
    {
        if ($id === null) {
            if ($result = $this->query('SELECT * FROM `' . static::TAB_NAME . '`')) {
                $ret = [];
                foreach ($result as $tuple) {
                    $ret[] = &$tuple;
                }
                return $ret;
            } else {
                throw new \Exception('Query failed: #' . $this->errno . ' - ' . $this->error);
            }
        } else {
            if ($just_pass_wd === true) {
                return $this->readPassWd($id);
            }

            if (!($stmt = $this->prepare('SELECT * FROM `' . static::TAB_NAME . '` WHERE `id` = ?'))) {
                throw new \Exception('Prepare failed: #' . $this->errno . ' - ' . $this->error);
            }
            if (!$stmt->bind_param('i', $id)) {
                throw new \Exception('Bind param failed: #' . $stmt->errno . ' - ' . $stmt->error);
            }

            echo 'Now fetching the table named ', static::TAB_NAME, PHP_EOL;
            if (!$stmt->execute()) {
                throw new \Exception('Execution failed: #' . $stmt->errno . ' - ' . $stmt->error);
            }

            // Standard operation
            //if (($meta = $stmt->result_metadata()) === false) {
            //    throw new \Exception('Getting meta data failed: #' . $stmt->errno . ' - ' . $stmt->error);
            //}
            //
            //if ($meta_result = $meta->fetch_fields()) {
            //    $field_list_name = [];
            //    foreach ($meta_result as &$field_info) {
            //        $field_list_name[] = $field_info->name;
            //    }
            //
            //    $param_list = [];
            //    $tuple = array_fill(0, $meta->field_count, null);
            //    foreach ($tuple as &$field_content) {
            //        $param_list[] = &$field_content;
            //    }
            //    call_user_func_array([$stmt, 'bind_result'], array_merge($param_list));
            //    if ($stmt->fetch() === false) {
            //        throw new \Exception('Fetching tuple data failed: #' . $stmt->errno . ' - ' . $stmt->error);
            //    } else {
            //        return [
            //            'field_list_name' => &$field_list_name,
            //            'tuple' => &$tuple,
            //        ];
            //    }
            //} else {
            //    throw new \Exception('Fetching meta data failed: #' . $stmt->errno . ' - ' . $stmt->error);
            //}

            // An easy way to achieve
            if ($result = $stmt->get_result()) {
                $ret = [];
                foreach ($result as $tuple) {
                    $ret[] = &$tuple;
                }
                return $ret;
            } else {
                throw new \Exception('Getting result failed: #' . $stmt->errno . ' - ' . $stmt->error);
            }
        }
    }

    /**
     * @param $id int Tuple id
     * @return int
     * @throws \Exception
     */
    public function delete($id)
    {
        if (!($stmt = $this->prepare('DELETE FROM `' . static::TAB_NAME . '` WHERE `id` = ?'))) {
            throw new \Exception('Prepare failed: #' . $this->errno . ' - ' . $this->error);
        }
        if (!$stmt->bind_param('i', $id)) {
            throw new \Exception('Bind param failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }

        if ($stmt->execute()) {
            return $stmt->affected_rows;
        } else {
            throw new \Exception('Execution failed: #' . $stmt->errno . ' - ' . $stmt->error);
        }
    }

    //endregion
}
